package com.siashan.toolkit.crypt.digest;


import com.siashan.toolkit.crypt.binary.Hex;
import com.siashan.toolkit.crypt.util.StringUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * Hmac算法工具类
 *
 * @author siashan
 * @since 1.0.7
 */
public final class HmacUtil {

    /**
     * 流缓冲大小
     */
    private static final int STREAM_BUFFER_LENGTH = 1024;


    /**
     * Mac实例
     */
    private final Mac mac;

//---------------------------------------------  构造方法  ------------------------------------------------------------//

    /**
     * 构造方法
     *
     * @param mac   Mac实例
     */
    private HmacUtil(final Mac mac) {
        this.mac = mac;
    }

    /**
     * 构造方法
     *
     * @param algorithm 算法名称
     * @param  key 秘钥
     * @since 1.0.7
     */
    public HmacUtil(final String algorithm, final byte[] key) {
        this(getInitializedMac(algorithm, key));
    }

    /**
     * 构造方法
     *
     * @param algorithm 算法名称
     * @param  key 秘钥
     * @since 1.0.7
     */
    public HmacUtil(final String algorithm, final String key) {
        this(algorithm, StringUtils.getBytesUtf8(key));
    }

    /**
     * 构造方法
     *
     * @param algorithm 算法名称
     * @param  key 秘钥
     * @since 1.0.7
     */
    public HmacUtil(final HmacAlgorithm algorithm, final String key) {
        this(algorithm.getName(), StringUtils.getBytesUtf8(key));
    }

    /**
     * 构造方法
     *
     * @param algorithm 算法名称
     * @param  key 秘钥
     * @since 1.0.7
     */
    public HmacUtil(final HmacAlgorithm algorithm, final byte[] key) {
        this(algorithm.getName(), key);
    }

    /**
     * 判断hmac算法是否可用
     *
     * @param name hmac算法名称
     * @return 是否可用
     * @since 1.0.7
     */
    public static boolean isAvailable(final String name) {
        try {
            Mac.getInstance(name);
            return true;
        } catch (final NoSuchAlgorithmException e) {
            return false;
        }
    }

    /**
     * 判断hmac算法是否可用
     *
     * @param name hmac算法
     * @return 是否可用
     * @since 1.0.7
     */
    public static boolean isAvailable(final HmacAlgorithm name) {
        return isAvailable(name.getName());
    }


    /**
     * 获取Mac实例
     *
     * @param algorithm  算法
     * @param key   秘钥
     * @return Mac 实例
     * @since 1.0.7
     */
    public static Mac getInitializedMac(final HmacAlgorithm algorithm, final byte[] key) {
        return getInitializedMac(algorithm.getName(), key);
    }

    /**
     * 获取Mac实例
     *
     * @param algorithm  算法
     * @param key   秘钥
     * @return Mac 实例
     * @since 1.0.7
     */
    public static Mac getInitializedMac(final String algorithm, final byte[] key) {

        if (key == null) {
            throw new IllegalArgumentException("Null key");
        }

        try {
            final SecretKeySpec keySpec = new SecretKeySpec(key, algorithm);
            final Mac mac = Mac.getInstance(algorithm);
            mac.init(keySpec);
            return mac;
        } catch (final NoSuchAlgorithmException e) {
            throw new IllegalArgumentException(e);
        } catch (final InvalidKeyException e) {
            throw new IllegalArgumentException(e);
        }
    }

//---------------------------------------  更新 Mac ------------------------------------------------------------------//

    /**
     * 重置并更新Mac
     *
     * @param mac Mac实例
     * @param valueToDigest 用于更新{@link Mac}的值
     * @return 更新后的Mac
     */
    public static Mac updateHmac(final Mac mac, final byte[] valueToDigest) {
        mac.reset();
        mac.update(valueToDigest);
        return mac;
    }

    /**
     * 重置并更新Mac
     *
     * @param mac   Mac实例
     * @param valueToDigest     用于更新{@link Mac}的值
     * @return 更新后的Mac
     * @throws IOException  IO异常
     */
    public static Mac updateHmac(final Mac mac, final InputStream valueToDigest) throws IOException {
        mac.reset();
        final byte[] buffer = new byte[STREAM_BUFFER_LENGTH];
        int read = valueToDigest.read(buffer, 0, STREAM_BUFFER_LENGTH);

        while (read > -1) {
            mac.update(buffer, 0, read);
            read = valueToDigest.read(buffer, 0, STREAM_BUFFER_LENGTH);
        }

        return mac;
    }

    /**
     * 重置并更新Mac
     *
     * @param mac   Mac实例
     * @param valueToDigest     用于更新{@link Mac}的值
     * @return 更新后的Mac
     * @throws IOException  IO异常
     */
    public static Mac updateHmac(final Mac mac, final String valueToDigest) {
        mac.reset();
        mac.update(StringUtils.getBytesUtf8(valueToDigest));
        return mac;
    }

//---------------------------------------------  hmac  ---------------------------------------------------------------//
    /**
     * hmac算法.
     *
     * @param valueToDigest 要加密的值
     * @return 加密后的值
     * @since 1.0.7
     */
    public byte[] hmac(final byte[] valueToDigest) {
        return mac.doFinal(valueToDigest);
    }

    /**
     * hmac算法.
     *
     * @param valueToDigest 要加密的值
     * @return 加密后的值
     * @since 1.0.7
     */
    public String hmacHex(final byte[] valueToDigest) {
        return Hex.encodeHexString(hmac(valueToDigest));
    }

    /**
     * hmac算法.
     *
     * @param valueToDigest 要加密的值
     * @return 加密后的值
     * @since 1.0.7
     */
    public byte[] hmac(final String valueToDigest) {
        return mac.doFinal(StringUtils.getBytesUtf8(valueToDigest));
    }

    /**
     * hmac算法.
     *
     * @param valueToDigest 要加密的值
     * @return 加密后的值
     * @since 1.0.7
     */
    public String hmacHex(final String valueToDigest) {
        return Hex.encodeHexString(hmac(valueToDigest));
    }

    /**
     * hmac算法.
     *
     * @param valueToDigest 要加密的值
     * @return 加密后的值
     * @since 1.0.7
     */
    public byte[] hmac(final ByteBuffer valueToDigest) {
        mac.update(valueToDigest);
        return mac.doFinal();
    }

    /**
     * hmac算法.
     *
     * @param valueToDigest 要加密的值
     * @return 加密后的值
     * @since 1.0.7
     */
    public String hmacHex(final ByteBuffer valueToDigest) {
        return Hex.encodeHexString(hmac(valueToDigest));
    }

    /**
     * hmac算法.
     *
     * @param valueToDigest 要加密的值
     * @return 加密后的值
     * @throws IOException   IO异常
     * @since 1.0.7
     */
    public byte[] hmac(final InputStream valueToDigest) throws IOException {
        final byte[] buffer = new byte[STREAM_BUFFER_LENGTH];
        int read;

        while ((read = valueToDigest.read(buffer, 0, STREAM_BUFFER_LENGTH)) > -1) {
            mac.update(buffer, 0, read);
        }
        return mac.doFinal();
    }

    /**
     * hmac算法.
     *
     * @param valueToDigest 要加密的值
     * @return 加密后的值
     * @throws IOException   IO异常
     * @since 1.0.7
     */
    public String hmacHex(final InputStream valueToDigest) throws IOException {
        return Hex.encodeHexString(hmac(valueToDigest));
    }

    /**
     * hmac算法.
     *
     * @param valueToDigest 要加密的值
     * @return 加密后的值
     * @throws IOException   IO异常
     * @since 1.0.7
     */
    public byte[] hmac(final File valueToDigest) throws IOException {
        try (final BufferedInputStream stream = new BufferedInputStream(new FileInputStream(valueToDigest))) {
            return hmac(stream);
        }
    }

    /**
     * hmac算法.
     *
     * @param valueToDigest 要加密的值
     * @return 加密后的值
     * @throws IOException   IO异常
     * @since 1.0.7
     */
    public String hmacHex(final File valueToDigest) throws IOException {
        return Hex.encodeHexString(hmac(valueToDigest));
    }

}
